package com.springboot.frame.excel;

import com.springboot.frame.excel.annotation.*;
import com.springboot.frame.excel.entity.MergeEntity;
import com.springboot.frame.excel.entity.WriterParam;
import com.springboot.frame.excel.entity.WriterSheet;
import com.springboot.frame.excel.enums.Excels;
import com.springboot.frame.excel.exception.ExcelExportException;
import com.springboot.frame.excel.handler.CellHandler;
import com.springboot.frame.excel.handler.CellTypeManager;
import com.springboot.frame.excel.handler.StringCellHandler;
import com.springboot.frame.excel.style.CellStyleFactory;
import com.springboot.frame.excel.util.ReflectionKit;
import com.springboot.frame.excel.util.SupportKit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.util.IOUtils;
import java.beans.IntrospectionException;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.Map.Entry;

/**
 * @author madman
 */
@Slf4j
public class ExcelWriter {

  /**
   * Workbook对象
   */
  private Workbook workbook;

  /**
   * CellStyle工厂
   */
  private CellStyleFactory cellStyFactory;

  private ExcelWriter(Workbook workbook) {
    this.workbook = workbook;
    this.cellStyFactory = new CellStyleFactory(workbook);
  }

  /**
   * 通过Excel类型创建Workbook对象
   *
   * @param excels Excel类型
   * @return ExcelWriter对象
   */
  public static ExcelWriter of(Excels excels) {
    try {
      return excels == null ? of()
          : new ExcelWriter((Workbook) Class.forName(excels.getName()).newInstance());
    } catch (ReflectiveOperationException e) {
      throw new ExcelExportException("创建Workbook失败", e);
    }
  }

  /**
   * 通过Excel类型创建Workbook对象
   *
   * @return ExcelWriter对象
   */
  public static ExcelWriter of() {
    return new ExcelWriter(new HSSFWorkbook());
  }

  /**
   * 导出Excel至内存
   *
   * @param data 导出数据
   * @return ExcelWriter对象
   */
  public ExcelWriter sheet(final Collection<?> data) {
    return sheet("", data);
  }

  /**
   * 导出Excel至内存
   *
   * @param title 表标题
   * @param data 导出数据
   * @return ExcelWriter对象
   */
  public ExcelWriter sheet(final String title, final Collection<?> data) {
    return sheet(title, data, null);
  }

  /**
   * 导出Excel至内存
   * @param title 表标题
   * @param data 导出数据
   * @param password 表格保护密码
   * @return
   */
  public ExcelWriter sheet(final String title, final Collection<?> data, String password) {
    if (CollectionUtils.isNotEmpty(data)) {
      Class<?> clazz = IterableUtils.get(data, 0).getClass();
      Excel excel =
              clazz.isAnnotationPresent(Excel.class) ? clazz.getAnnotation(Excel.class)
                      : CellStyleFactory.EXCEL_ANNOTATION;
      short titleHeight = excel.titleHeight();
      short headerHeight = excel.headerHeight();
      short bodyHeight = excel.bodyHeight();
      Sheet sheet =
              StringUtils.isBlank(excel.sheetName()) ? workbook.createSheet()
                      : workbook.createSheet(excel.sheetName());
      if(StringUtils.isNotBlank(password)) {
        sheet.protectSheet(password);
      }
      List<WriterParam> writerParams = initExcelParams(sheet, clazz, 0);
      WriterSheet writerSheet = new WriterSheet(sheet, clazz, data, title, titleHeight,
              headerHeight, bodyHeight, 0,
              writerParams);
      createTitle(writerSheet);
      createHeader(writerSheet);
      createBody(writerSheet, 0);
    }
    return this;
  }

  /**
   * 把内存中Excel写入文件
   *
   * @param out 输出流
   */
  public void write(OutputStream out) {
    try {
      workbook.write(out);
    } catch (IOException e) {
      throw new ExcelExportException("写入文件失败", e);
    } finally {
      IOUtils.closeQuietly(out);
      IOUtils.closeQuietly(workbook);
    }
  }

  /**
   * 初始化所有的导出参数
   *
   * @param sheet Sheet对象
   * @param dataClass 导出对象Class类型
   * @param columnIndex 列索引
   * @return 导出参数集
   */
  private List<WriterParam> initExcelParams(Sheet sheet, Class<?> dataClass, int columnIndex) {
    try {
      List<WriterParam> writerParams = new ArrayList<>();
      for (Entry<Field, Method> entry : ReflectionKit.fieldAndGetterMethod(dataClass).entrySet()) {
        Field field = entry.getKey();
        Method method = entry.getValue();
        Class<?> fieldType = field.getType();
        if (!(field.isAnnotationPresent(ExcelIgnore.class) && field.getAnnotation(ExcelIgnore.class)
            .ignoreWrite())) {
          if (Collection.class.isAssignableFrom(fieldType)) {
            WriterParam writerParam = new WriterParam();
            ExcelCollection excelCollection = field.getAnnotation(ExcelCollection.class);
            writerParam
                .setHeaderName(excelCollection == null ? field.getName() : excelCollection.value());
            writerParam.setMethod(method);
            boolean annotationPresent = field.isAnnotationPresent(HeaderStyle.class);
            HeaderStyle headerStyle =
                annotationPresent ? field.getAnnotation(HeaderStyle.class)
                    : CellStyleFactory.HEADER_ANNOTATION;
            writerParam.setHeaderStyle(cellStyFactory.createHeaderCellStyle(headerStyle));
            Class<?> sonClazz = (Class<?>) ((ParameterizedType) field.getGenericType())
                .getActualTypeArguments()[0];
            writerParam.setChildren(initExcelParams(sheet, sonClazz, columnIndex++));
            writerParams.add(writerParam);
          } else if (SupportKit.support(fieldType)) {
            writerParams.add(createExcelParam(sheet, field, method, columnIndex++));
          } else {
            throw new ExcelExportException("不支持类型：" + fieldType.getName());
          }
        }
      }
      return writerParams;
    } catch (IntrospectionException e) {
      throw new ExcelExportException("反射失败", e);
    }
  }

  /**
   * 创建导出数据
   *
   * @param sheet Sheet对象
   * @param field 字段
   * @param method Getter方法
   * @param columnIndex 列索引
   * @return 导出数据
   */
  private WriterParam createExcelParam(Sheet sheet, Field field, Method method, int columnIndex) {
    boolean excelAttributePresent = field.isAnnotationPresent(ExcelAttribute.class);
    ExcelAttribute attribute =
        excelAttributePresent ? field.getAnnotation(ExcelAttribute.class)
            : CellStyleFactory.ATTRIBUTE_ANNOTATION;
    WriterParam writerParam = new WriterParam();
    writerParam.setHeaderName(
        StringUtils.isBlank(attribute.value()) ? field.getName() : attribute.value());
    boolean headerStylePresent = field.isAnnotationPresent(HeaderStyle.class);
    HeaderStyle headerStyle =
        headerStylePresent ? field.getAnnotation(HeaderStyle.class)
            : CellStyleFactory.HEADER_ANNOTATION;
    writerParam.setHeaderStyle(cellStyFactory.createHeaderCellStyle(headerStyle));
    writerParam.setMethod(method);
    writerParam.setColumnIndex(columnIndex);
    writerParam.setCellStyle(cellStyFactory.createAttributeCellStyle(field, attribute));
    try {
      Class<? extends CellHandler> typeHandler = attribute.typeHandler();
      boolean equals = CellHandler.class.equals(typeHandler);
      writerParam.setCellHandler(
          equals ? CellTypeManager.getHandler(field.getType()) : typeHandler.newInstance());
    } catch (ReflectiveOperationException e) {
      throw new ExcelExportException("反射创建typeHandler失败", e);
    }
    writerParam.setNeedMerge(attribute.needMerge());
    sheet.setColumnWidth(columnIndex, attribute.width() * 256);
    return writerParam;
  }

  /**
   * 创建表标题
   *
   * @param writerSheet WriterSheet对象
   */
  private void createTitle(WriterSheet writerSheet) {
    String title = writerSheet.getTitle();
    if (StringUtils.isNotBlank(title)) {
      Class<?> clazz = writerSheet.getClazz();
      TitleStyle titleStyle =
          clazz.isAnnotationPresent(TitleStyle.class) ? clazz.getAnnotation(TitleStyle.class)
              : CellStyleFactory.TITLE_ANNOTATION;
      CellStyle titleCellStyle = cellStyFactory.createTitleCellStyle(titleStyle);
      Sheet sheet = writerSheet.getSheet();
      Row row = sheet.createRow(0);
      row.setHeightInPoints(writerSheet.getTitleHeight());
      StringCellHandler.INSTANCE.write(row, 0, title, titleCellStyle);
      int columnCount = 0;
      for (WriterParam writerParam : writerSheet.getWriterParams()) {
        List<WriterParam> children = writerParam.getChildren();
        columnCount += CollectionUtils.isNotEmpty(children) ? children.size() : 1;
      }
      for (int columnIndex = 1; columnIndex < columnCount; columnIndex++) {
        row.createCell(columnIndex).setCellStyle(titleCellStyle);
      }
      sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, columnCount - 1));
      writerSheet.increaseIndex();
    }
  }

  /**
   * 创建表头
   *
   * @param writerSheet WriterSheet对象
   */
  private void createHeader(WriterSheet writerSheet) {
    Sheet sheet = writerSheet.getSheet();
    int rowIndex = writerSheet.getRowIndex();
    Row row = sheet.createRow(rowIndex);
    row.setHeightInPoints(writerSheet.getHeaderHeight());
    List<WriterParam> writerParams = writerSheet.getWriterParams();
    int headerRowNum = writerSheet.calculateHeaderRowNum();
    Row listRow = null;
    if (headerRowNum > 1) {
      listRow = sheet.createRow(rowIndex + 1);
      listRow.setHeightInPoints(writerSheet.getHeaderHeight());
    }
    int columnIndex = 0;
    for (WriterParam writerParam : writerParams) {
      CellStyle headerStyle = writerParam.getHeaderStyle();
      StringCellHandler.INSTANCE.write(row, columnIndex, writerParam.getHeaderName(), headerStyle);
      if (CollectionUtils.isNotEmpty(writerParam.getChildren())) {
        List<WriterParam> sonWriterParams = writerParam.getChildren();
        if (StringUtils.isNotBlank(writerParam.getHeaderName())) {
          for (int i = columnIndex + 1; i < columnIndex + sonWriterParams.size(); i++) {
            row.createCell(i).setCellStyle(headerStyle);
          }
          sheet.addMergedRegion(
              new CellRangeAddress(rowIndex, rowIndex, columnIndex,
                  columnIndex + sonWriterParams.size() - 1));
        }
        for (WriterParam sonWriterParam : sonWriterParams) {
          StringCellHandler.INSTANCE
              .write(listRow, columnIndex++, sonWriterParam.getHeaderName(),
                  sonWriterParam.getHeaderStyle());
        }
        columnIndex--;
      } else if (listRow != null) {
        listRow.createCell(columnIndex).setCellStyle(headerStyle);
        sheet.addMergedRegion(
            new CellRangeAddress(rowIndex, rowIndex + 1, columnIndex, columnIndex));
      }
      columnIndex++;
    }
    writerSheet.setRowIndex(rowIndex + headerRowNum);
  }

  /**
   * 创建表内容
   *
   * @param writerSheet WriterSheet对象
   * @param columnIndex 列索引
   */
  @SuppressWarnings({"rawtypes", "unchecked"})
  private void createBody(WriterSheet writerSheet, int columnIndex) {
    Sheet sheet = writerSheet.getSheet();
    int rowIndex = writerSheet.getRowIndex();
    List<WriterParam> writerParams = writerSheet.getWriterParams();
    Object beforeEntity = null;
    int originColumnIndex = columnIndex;
    Map<Integer, MergeEntity> mergeMap = new HashMap<>();
    for (Object entity : writerSheet.getData()) {
      Row row = columnIndex > 0 ? sheet.getRow(rowIndex) : sheet.createRow(rowIndex);
      row.setHeightInPoints(writerSheet.getBodyHeight());
      int dataRowNum = getDataRowNum(entity, writerParams);
      for (WriterParam writerParam : writerParams) {
        Method method = writerParam.getMethod();
        if (CollectionUtils.isNotEmpty(writerParam.getChildren())) {
          Collection<?> sonList = (Collection<?>) ReflectionKit.getRefValue(entity, method);
          if (CollectionUtils.isNotEmpty(sonList)) {
            createBody(
                new WriterSheet(sheet, sonList, writerParam.getChildren(),
                    writerSheet.getBodyHeight(), rowIndex),
                columnIndex);
          }
          columnIndex += writerParam.getChildren().size();
        } else {
          Object value = ReflectionKit.getRefValue(entity, method);
          writerParam.getCellHandler().write(row, columnIndex, value, writerParam.getCellStyle());
          for (int i = rowIndex + 1; i < rowIndex + dataRowNum; i++) {
            Row otherRow = sheet.getRow(i) == null ? sheet.createRow(i) : sheet.getRow(i);
            otherRow.createCell(columnIndex).setCellStyle(writerParam.getCellStyle());
          }
          if (writerParam.isNeedMerge()) {
            if (mergeMap.containsKey(columnIndex)) {
              if (Objects.equals(ReflectionKit.getRefValue(beforeEntity, method), value)) {
                mergeMap.get(columnIndex).setEndRow(rowIndex + dataRowNum - 1);
              } else {
                MergeEntity mergeEntity = mergeMap.get(columnIndex);
                if ((mergeEntity.getEndRow() - mergeEntity.getStartRow()) > 0) {
                  sheet.addMergedRegion(
                      new CellRangeAddress(mergeEntity.getStartRow(), mergeEntity.getEndRow(),
                          columnIndex,
                          columnIndex));
                }
                mergeEntity.setStartRow(rowIndex);
                mergeEntity.setEndRow(rowIndex + dataRowNum - 1);
              }
            } else {
              mergeMap.put(columnIndex, new MergeEntity(rowIndex, rowIndex + dataRowNum - 1));
            }
          } else if (dataRowNum > 1) {
            sheet.addMergedRegion(
                new CellRangeAddress(rowIndex, rowIndex + dataRowNum - 1, columnIndex,
                    columnIndex));
          }
          columnIndex++;
        }
      }
      columnIndex = originColumnIndex;
      rowIndex += dataRowNum;
      beforeEntity = entity;
    }
    for (Entry<Integer, MergeEntity> entry : mergeMap.entrySet()) {
      Integer mergeColumn = entry.getKey();
      MergeEntity mergeEntity = entry.getValue();
      if ((mergeEntity.getEndRow() - mergeEntity.getStartRow()) > 0) {
        sheet.addMergedRegion(
            new CellRangeAddress(mergeEntity.getStartRow(), mergeEntity.getEndRow(), mergeColumn,
                mergeColumn));
      }
    }
  }

  /**
   * 获取该对象对应的行数
   *
   * @param data 对象
   * @param writerParams 数据参数集
   * @return 行数
   */
  private int getDataRowNum(Object data, List<WriterParam> writerParams) {
    int dataRowNum = 1;
    for (WriterParam writerParam : writerParams) {
      if (writerParam.getChildren() != null) {
        Collection<?> sonList = (Collection<?>) ReflectionKit
            .getRefValue(data, writerParam.getMethod());
        if (sonList != null) {
          dataRowNum = Math.max(dataRowNum, sonList.size());
        }
      }
    }
    return dataRowNum;
  }

}
